using System;
using System.Collections.Generic;
using System.Reflection;
using Kopernikus.Collections.Generic;
using Kopernikus.Parsing.GenericActions;
using Kopernikus.Parsing.Lexing;

namespace Kopernikus.Parsing.GenericLexing
{
    public class Evaluator<TLexer, TToken> : ILexerEvaluator<GenericMatch<TToken>, ILexerAction<TLexer, TToken>>
    {
        private readonly EvaluatorData<TLexer, TToken> m_evaluatorData;
        private readonly TLexer m_instance;
        private readonly Dictionary<string, Symbol> m_symbolCache;

        private Evaluator(TLexer instance, EvaluatorData<TLexer, TToken> evaluatorData)
        {
            m_instance = instance;
            m_evaluatorData = evaluatorData;
            m_symbolCache = new Dictionary<string, Symbol>();
        }

        public Evaluator(Type t)
        {
            LexerBuilder<GenericMatch<TToken>, ILexerAction<TLexer, TToken>> builder =
                new LexerBuilder<GenericMatch<TToken>, ILexerAction<TLexer, TToken>>();
            Set<Symbol> symbols = new Set<Symbol>();

            foreach (
                LexIgnoreAttribute whiteSpaceAttribute in t.GetCustomAttributes(typeof (LexIgnoreAttribute), true))
            {
                builder.AddIgnoreRule(whiteSpaceAttribute.Pattern);
            }

            foreach (MethodInfo methodInfo in t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic |
                                                           BindingFlags.Instance | BindingFlags.Static |
                                                           BindingFlags.InvokeMethod))
            {
                foreach (
                    LexAttribute terminalAttribute in
                        methodInfo.GetCustomAttributes(typeof (LexAttribute), true))
                {
                    Symbol terminalSymbol = symbols.Add(new Symbol(terminalAttribute.Symbol));

                    builder.AddRule(terminalAttribute.Pattern,
                                    new LexerAction<TLexer, TToken>(methodInfo, terminalSymbol));
                }

                foreach (
                    LexDefaultAttribute defaultLexerAttribute in
                        methodInfo.GetCustomAttributes(typeof (LexDefaultAttribute), true))
                {
                    if (defaultLexerAttribute.LiteralTerminals == null)
                    {
                        builder.AddDefaultSingleCharacterRule(new DefaultLexerAction<TLexer, TToken>(methodInfo));
                    }
                    else
                    {
                        foreach (string literal in defaultLexerAttribute.LiteralTerminals)
                        {
                            Symbol literalSymbol = symbols.Add(new Symbol(literal));

                            builder.AddDefaultRule(literal, new LexerAction<TLexer, TToken>(methodInfo, literalSymbol));
                        }
                    }
                }

                foreach (
                    LexKeywordAttribute keywordAttribute in
                        methodInfo.GetCustomAttributes(typeof (LexKeywordAttribute), true)
                    )
                {
                    Symbol keywordSymbol = symbols.Add(new Symbol(keywordAttribute.Symbol));
                    builder.AddKeywordRule(keywordAttribute.Symbol,
                                           new LexerAction<TLexer, TToken>(methodInfo, keywordSymbol));
                }
            }
            m_evaluatorData = new EvaluatorData<TLexer, TToken>(builder.CreateLexer());
        }


        public Lexer<GenericMatch<TToken>, ILexerAction<TLexer, TToken>> Lexer
        {
            get { return m_evaluatorData.Lexer; }
        }

        #region ILexerEvaluator<GenericMatch<TToken>,ILexerAction<TLexer,TToken>> Members

        public bool TryEvaluate(ILexerAction<TLexer, TToken> action, string value, int pos, int line, int col,
                                out GenericMatch<TToken> token)
        {
            if ((action != null) && action.CanInvoke)
            {
                LexerMatch m = new LexerMatch(action.GetSymbol(value, m_symbolCache), value, pos, line, col);
                token = new GenericMatch<TToken>(action.Invoke(m_instance, m), m);
                return true;
            }
            else
            {
                token = null;
                return false;
            }
        }

        #endregion

        public Evaluator<TLexer, TToken> Clone(TLexer instance)
        {
            return new Evaluator<TLexer, TToken>(instance, m_evaluatorData);
        }
    }
}